import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

/// Function type for loading more videos when scrolling
typedef LoadMoreVideos = Future<List<VideoPlayerItem>> Function(int currentIndex, List<VideoPlayerItem> currentList);

/// Main controller that manages a list of video players for a feed
class VideoFeedController extends ChangeNotifier {
  final int _preloadCount; // Number of videos to preload ahead
  final int _disposeOffset; // How far from current video to dispose resources

  // Current position in the video feed
  final ValueNotifier<int> currentIndex = ValueNotifier<int>(0);

  // List of all video player items
  final List<VideoPlayerItem> _videoItems = [];

  // Singleton lock for synchronizing video operations
  static Completer<void>? _operationLock;

  VideoFeedController({int preloadCount = 2, int disposeOffset = 0})
      : _preloadCount = preloadCount,
        _disposeOffset = disposeOffset;

  // Initialize the controller with a page controller and initial videos
  Future<void> initialize({required PageController pageController, required List<VideoPlayerItem> initialVideos}) async {
    // Add initial videos to our list
    _videoItems.addAll(initialVideos);

    // Listen for page changes to update the active video
    pageController.addListener(() {
      final page = pageController.page!;
      if (page % 1 == 0) {
        // Only process complete page transitions
        navigateToVideo(page.toInt());
      }
    });

    // Start playing the first video
    navigateToVideo(0, isInitialLoad: true);
    notifyListeners();
  }

  /// Navigate to a specific video index
  Future<void> navigateToVideo(int targetIndex, {bool isInitialLoad = false}) async {
    if (!isInitialLoad && currentIndex.value == targetIndex) return;

    final previousIndex = currentIndex.value;

    // Handle previous video
    if (previousIndex != targetIndex) {
      final previousVideo = _getVideoAt(previousIndex);
      if (previousVideo != null) {
        // Reset and pause previous video
        await previousVideo.controller.seekTo(Duration.zero);
        await previousVideo.pause();

        // Clean up listeners from previous video
        previousVideo.controller.removeListener(_onVideoStateChanged);
        previousVideo.pauseIndicatorVisible.removeListener(_onVideoStateChanged);
      }
    }

    // Handle new video
    final currentVideo = _getVideoAt(targetIndex);
    if (currentVideo != null) {
      // Add listeners to current video
      currentVideo.controller.addListener(_onVideoStateChanged);
      currentVideo.pauseIndicatorVisible.addListener(_onVideoStateChanged);

      // Play the current video
      await currentVideo.play();
    }

    // Update resource management for all videos
    _manageVideoResources(targetIndex);

    // Update current index
    currentIndex.value = targetIndex;
  }

  /// Manage resources by initializing upcoming videos and disposing distant ones
  void _manageVideoResources(int activeIndex) {
    for (var i = 0; i < _videoItems.length; i++) {
      // Dispose videos that are far away from current position
      final bool shouldDispose = i < activeIndex - _disposeOffset || i > activeIndex + math.max(_disposeOffset, 2);

      if (shouldDispose) {
        final video = _getVideoAt(i);
        if (video != null) {
          video.controller.removeListener(_onVideoStateChanged);
          video.pauseIndicatorVisible.removeListener(_onVideoStateChanged);
          video.dispose();
        }
        continue;
      }

      // Preload upcoming videos for smoother experience
      final bool shouldPreload = i > activeIndex && i < activeIndex + _preloadCount;
      if (shouldPreload) {
        _getVideoAt(i)?.initialize();
      }
    }
  }

  /// Get video at specified index if valid
  VideoPlayerItem? _getVideoAt(int index) {
    if (index < 0 || index >= _videoItems.length) return null;
    return _videoItems[index];
  }

  /// Notify listeners when video state changes
  void _onVideoStateChanged() {
    notifyListeners();
  }

  /// Get current video controller
  VideoPlayerItem? get currentVideo => currentIndex.value < _videoItems.length ? _videoItems[currentIndex.value] : null;

  /// Total number of videos in the feed
  int get videoCount => _videoItems.length;

  @override
  void dispose() {
    // Clean up all videos
    for (final video in _videoItems) {
      video.pauseIndicatorVisible.dispose();
      video.dispose();
    }
    _videoItems.clear();
    currentIndex.dispose();
    super.dispose();
  }
}

/// A base interface for video controllers
abstract class VideoControllerBase<T> {
  /// The underlying player controller
  T? get controller;

  /// Whether to show pause indicator
  ValueNotifier<bool> get pauseIndicatorVisible;

  /// Initialize the video
  Future<void> initialize();

  /// Clean up resources
  Future<void> dispose();

  /// Start playback
  Future<void> play();

  /// Pause playback
  Future<void> pause({bool showPauseIcon = false});
}

/// Creates and manages a single video player
class VideoPlayerItem extends VideoControllerBase<VideoPlayerController> {
  VideoPlayerController? _controller;
  final ValueNotifier<bool> _pauseIndicatorVisible = ValueNotifier<bool>(false);

  // Video metadata
  final String? videoInfo;

  // Callbacks for controller creation and initialization
  final VideoPlayerController Function() _createController;
  final Future<void> Function(VideoPlayerController)? _onInitialized;

  // State tracking
  bool _isInitialized = false;
  Completer<void>? _disposeCompleter;

  VideoPlayerItem({
    required VideoPlayerController Function() createController,
    Future<void> Function(VideoPlayerController)? onInitialized,
    this.videoInfo,
  })  : _createController = createController,
        _onInitialized = onInitialized;

  @override
  VideoPlayerController get controller {
    _controller ??= _createController();
    return _controller!;
  }

  @override
  ValueNotifier<bool> get pauseIndicatorVisible => _pauseIndicatorVisible;

  bool get isInitialized => _isInitialized;
  bool get isDisposing => _disposeCompleter != null;

  /// Synchronize async operations to prevent conflicts
  Future<void> _executeOperation(Future<void> Function()? operation) async {
    final previousOperation = VideoFeedController._operationLock;
    final currentOperation = Completer<void>();
    VideoFeedController._operationLock = currentOperation;

    // Wait for previous operation to complete
    await previousOperation?.future;

    // Execute the current operation
    if (operation != null) {
      await operation();
    }

    // Mark this operation as complete
    currentOperation.complete();
  }

  @override
  Future<void> initialize() async {
    if (_isInitialized) return;

    await _executeOperation(() async {
      await controller.initialize();
      await controller.setLooping(true);

      if (_onInitialized != null) {
        await _onInitialized(controller);
      }

      _isInitialized = true;
    });

    // If dispose was called during initialization, complete it now
    if (_disposeCompleter != null) {
      _disposeCompleter!.complete();
      _disposeCompleter = null;
    }
  }

  @override
  Future<void> dispose() async {
    if (!_isInitialized) return;

    _isInitialized = false;

    await _executeOperation(() async {
      await controller.dispose();
      _controller = null;
      _disposeCompleter = Completer<void>();
    });
  }

  @override
  Future<void> pause({bool showPauseIcon = false}) async {
    await initialize();
    if (!_isInitialized) return;

    if (_disposeCompleter != null) {
      await _disposeCompleter!.future;
    }

    await controller.pause();
    _pauseIndicatorVisible.value = showPauseIcon;
  }

  @override
  Future<void> play() async {
    await initialize();
    if (!_isInitialized) return;

    if (_disposeCompleter != null) {
      await _disposeCompleter!.future;
    }

    await controller.play();
    _pauseIndicatorVisible.value = false;
  }
}
